package seismosurfer.gui.charts;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import seismosurfer.data.MacroData;
import seismosurfer.data.QuakeClientData;
import seismosurfer.data.QuakeData;
import seismosurfer.data.constants.ColumnNames;
import seismosurfer.gui.GUIConstants;
import seismosurfer.layer.MacroseismicLayer;
import seismosurfer.util.SeismoException;
import seismosurfer.util.TimeUtil;
import aw.gui.chart.Chart2D;
import aw.gui.chart.ITrace2D;
import aw.gui.chart.Trace2DSorted;
import aw.gui.chart.TracePoint2D;

import com.bbn.openmap.gui.OMComponentPanel;
import com.bbn.openmap.gui.WindowSupport;
import com.bbn.openmap.util.Debug;

/**
 * A panel for choosing the attributes of the
 * client data that will be plotted.
 *
 */
public class ChartsPanel extends OMComponentPanel implements ActionListener,
        GUIConstants, ColumnNames {
    
    private static final long serialVersionUID = -5200584416678460794L;

    private MacroseismicLayer macroLayer;

    private String layerName;

    private String legend;

    private JComboBox xCombo;

    private JComboBox yCombo;

    private static final int DATETIME_IDX = 1;

    private static final int DEPTH_IDX = 2;

    private static final int MAGNITUDE_IDX = 3;

    private static final int INTENSITY_IDX = 1;

    private static final int POPULATION_IDX = 2;

    private static final int DISTANCE_IDX = 3;

    private static final int HYP_DISTANCE_IDX = 4;

    private XAxis xAxis = new XAxis();

    private YAxis yAxis = new YAxis();

    public ChartsPanel() {
        super();
        createWidgets(this);
    }

    protected void createWidgets(ActionListener buttonsListener) {

        // Top Panel
        JPanel topPanel = new JPanel();
        GridBagLayout gridBagLayout1 = new GridBagLayout();
        topPanel.setLayout(gridBagLayout1);

        xCombo = new JComboBox();
        JLabel messageLbl = new JLabel();
        yCombo = new JComboBox();
        JLabel yLbl = new JLabel();
        JLabel xLbl = new JLabel();

        messageLbl.setHorizontalAlignment(SwingConstants.CENTER);
        messageLbl.setHorizontalTextPosition(SwingConstants.CENTER);
        messageLbl.setText("Select the attributes for the X and Y axis. ");
        yLbl.setText("Y-axis");
        xLbl.setText("X-axis");

        topPanel.add(xCombo, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0,
                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
                new Insets(8, 6, 0, 16), 189, 3));
        topPanel.add(yCombo, new GridBagConstraints(0, 4, 1, 1, 1.0, 0.0,
                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
                new Insets(0, 6, 32, 16), 189, 3));
        topPanel.add(yLbl, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(
                        11, 6, 0, 157), 44, 12));
        topPanel.add(xLbl, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(6,
                        6, 0, 157), 43, 12));
        topPanel.add(messageLbl, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(
                        10, 6, 0, 5), 1, 10));

        // Bottom Panel

        JPanel bottomPanel = new JPanel();
        JButton okBtn = new JButton("OK");
        okBtn.addActionListener(this);
        okBtn.setActionCommand(OK);
        bottomPanel.add(okBtn);

        JButton cancelBtn = new JButton("Cancel");
        cancelBtn.addActionListener(this);
        cancelBtn.setActionCommand(CANCEL);
        bottomPanel.add(cancelBtn);

        setLayout(new BorderLayout());

        add(topPanel, BorderLayout.CENTER);
        add(bottomPanel, BorderLayout.SOUTH);
    }

    public void findAndInit(Object someObj) {

        if (someObj instanceof MacroseismicLayer) {
            macroLayer = (MacroseismicLayer) someObj;
        }

    }

    public void findAndUndo(Object someObj) {
    }
    
    /**
     * Fills the x and y combo boxes of the panel 
     * with the attributes of the specified layer
     * 
     * @param layer the name of the layer
     */
    public void showChartDialog(String layer) {

        layerName = layer;

        if (layerName.equalsIgnoreCase(QUAKE_LAYER)) {
            xCombo.removeAllItems();
            yCombo.removeAllItems();

            xCombo.addItem(DATETIME);
            xCombo.addItem(DEPTH);
            xCombo.addItem(MAGNITUDE);

            yCombo.addItem(DATETIME);
            yCombo.addItem(DEPTH);
            yCombo.addItem(MAGNITUDE);
        } else if (layerName.equalsIgnoreCase(MACRO_LAYER)) {
            xCombo.removeAllItems();
            yCombo.removeAllItems();

            xCombo.addItem(INTENSITY);
            xCombo.addItem(POPULATION);
            xCombo.addItem(DISTANCE);
            xCombo.addItem(HYP_DISTANCE);

            yCombo.addItem(INTENSITY);
            yCombo.addItem(POPULATION);
            yCombo.addItem(DISTANCE);
            yCombo.addItem(HYP_DISTANCE);
        }

        String title = "Make a Chart for " + layerName;
        if (getWindowSupport() == null) {
            setWindowSupport(new WindowSupport(this, title));
        } else {
            windowSupport.setTitle(title);
        }

        getWindowSupport().displayInWindow();
    }

    public void actionPerformed(ActionEvent ae) {

        String command = ae.getActionCommand();

        if ((command.equals(OK)) && (getWindowSupport() != null)) {

            xAxis.setLegend(XAxis.X + (String) xCombo.getSelectedItem());
            yAxis.setLegend(YAxis.Y + (String) yCombo.getSelectedItem());

            Chart2D chart = makeChart(xCombo.getSelectedIndex() + 1, yCombo
                    .getSelectedIndex() + 1);

            showChartFrame(chart);

            getWindowSupport().killWindow();

        } else if ((command.equals(CANCEL)) && (getWindowSupport() != null)) {
            getWindowSupport().killWindow();
        }

    }
    
    /**
     * Creates a panel that plots the values 
     * of the attributes of a layer.
     * 
     * @param x the selected attribute in the X axis
     * @param y the selected attribute in the Y axis
     * @return a Chart2D panel
     */
    protected Chart2D makeChart(int x, int y) {

        Chart2D chart = new Chart2D();
        ITrace2D trace = new Trace2DSorted();

        trace.setColor(Color.RED);

        if (layerName.equalsIgnoreCase(QUAKE_LAYER)) {
            setQuakeTracePoints(x, y, trace);
        } else if (layerName.equalsIgnoreCase(MACRO_LAYER)) {
            setMacroTracePoints(x, y, trace);
        }

        legend = xAxis.getLegend() + yAxis.getLegend();
        trace.setLabel(legend);
        trace.setName(legend);

        chart.addTrace(trace);

        return chart;
    }
    
    /**
     * Fills a ITrace2D object with macroseismic data
     * that will be plotted.
     * 
     * @param xAxis the selected attribute in the X axis
     * @param yAxis the selected attribute in the Y axis
     * @param trace the ITrace2D object that will be plotted
     */
    protected void setMacroTracePoints(int xAxis, int yAxis, ITrace2D trace) {

        java.util.List macroList = macroLayer.getMacroDataList();
        int size = macroList.size();

        java.util.List chartList = new ArrayList(size);

        switch (xAxis) {

        case INTENSITY_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                chartList.add(new TracePoint2D(trace, md.getIntensity(), 0));
            }
            break;

        case POPULATION_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                chartList.add(new TracePoint2D(trace, md.getPopulation(), 0));
            }
            break;

        case DISTANCE_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                chartList.add(new TracePoint2D(trace, md.getDistance(), 0));
            }
            break;
        case HYP_DISTANCE_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                chartList.add(new TracePoint2D(trace, md
                        .getHypocenterDistance(), 0));
            }
            break;
        default:
            Debug.error("Request is not in the expected form.");
            throw new SeismoException("Request is not in the expected form.");
        }

        switch (yAxis) {

        case INTENSITY_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), md.getIntensity());

                chartList.set(i, point);
            }
            break;

        case POPULATION_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), md.getPopulation());

                chartList.set(i, point);
            }
            break;
        case DISTANCE_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), md.getDistance());

                chartList.set(i, point);
            }
            break;
        case HYP_DISTANCE_IDX:
            for (int i = 0; i < size; i++) {
                MacroData md = (MacroData) macroList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), md.getHypocenterDistance());

                chartList.set(i, point);
            }
            break;
        default:
            Debug.error("Request is not in the expected form.");
            throw new SeismoException("Request is not in the expected form.");
        }

        for (int i = 0; i < size; i++) {
            trace.addPoint((TracePoint2D) (chartList.get(i)));
        }

        chartList.clear();
    }
    
    /**
     * Fills a ITrace2D object with quake data
     * that will be plotted.
     * 
     * @param xAxis the selected attribute in the X axis
     * @param yAxis the selected attribute in the Y axis
     * @param trace the ITrace2D object that will be plotted
     */
    protected void setQuakeTracePoints(int xAxis, int yAxis, ITrace2D trace) {

        java.util.List quakesList = QuakeClientData.getQuakeResults();
        int size = quakesList.size();

        java.util.List chartList = new ArrayList(size);

        switch (xAxis) {

        case DATETIME_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                chartList.add(new TracePoint2D(trace, qd.getDateTime(), 0));
            }
            break;

        case DEPTH_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                chartList.add(new TracePoint2D(trace, qd.getDepth(), 0));
            }
            break;

        case MAGNITUDE_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                chartList.add(new TracePoint2D(trace, qd.getMagnitude(), 0));
            }
            break;

        default:
            Debug.error("Request is not in the expected form.");
            throw new SeismoException("Request is not in the expected form.");
        }

        switch (yAxis) {

        case DATETIME_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), qd.getDateTime());

                chartList.set(i, point);
            }
            break;

        case DEPTH_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), qd.getDepth());

                chartList.set(i, point);
            }
            break;

        case MAGNITUDE_IDX:
            for (int i = 0; i < size; i++) {
                QuakeData qd = (QuakeData) quakesList.get(i);
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                point.setLocation(point.getX(), qd.getMagnitude());

                chartList.set(i, point);
            }
            break;

        default:
            Debug.error("Request is not in the expected form.");
            throw new SeismoException("Request is not in the expected form.");

        }

        if (xAxis == DATETIME_IDX) {
            chartList = handleXaxisDates(chartList);
        }

        if (yAxis == DATETIME_IDX) {
            chartList = handleYaxisDates(chartList);
        }

        for (int i = 0; i < size; i++) {
            trace.addPoint((TracePoint2D) (chartList.get(i)));
        }

        chartList.clear();
    }

    private java.util.List handleYaxisDates(java.util.List chartList) {
        yAxis = new YAxis(chartList);
        return handleDates(yAxis);
    }

    class XAxis extends Axis {

        public static final String X = " X: ";

        public XAxis(java.util.List chartData) {
            super(chartData);
        }

        public XAxis() {

        }
        
        
        public void setTime(Legend legend) {

            label = X + legend.getLabel();

            int size = chartList.size();

            for (int i = 0; i < size; i++) {
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                long time = (long) point.getX();

                time = legend.getTime(time);

                point.setLocation(time, point.getY());

                chartList.set(i, point);
            }
        }

    }

    class YAxis extends Axis {

        private static final String Y = ", Y: ";

        public YAxis(java.util.List chartData) {
            super(chartData);
        }

        public YAxis() {

        }

        public void setTime(Legend legend) {

            label = Y + legend.getLabel();

            int size = chartList.size();

            for (int i = 0; i < size; i++) {
                TracePoint2D point = (TracePoint2D) chartList.get(i);
                long time = (long) point.getY();

                time = legend.getTime(time);

                point.setLocation(point.getX(), time);

                chartList.set(i, point);
            }
        }

    }

    private java.util.List handleDates(Axis axis) {

        long millis = QuakeClientData.getMaxTime()
                - QuakeClientData.getMinTime();

        if (TimeUtil.toCenturies(millis) > 4) {
            axis.setCenturies();
        } else if (TimeUtil.toYears(millis) > 3) {
            axis.setYears();
        } else if (TimeUtil.toMonths(millis) > 3) {
            axis.setMonths();
        } else if (TimeUtil.toDays(millis) > 3) {
            axis.setDays();
        } else {
            axis.setHours();
        }

        return axis.getChartList();

    }

    private java.util.List handleXaxisDates(java.util.List chartList) {
        xAxis = new XAxis(chartList);
        return handleDates(xAxis);
    }
    
    /**
     * Shows the given chart panel on a JFrame.
     * 
     * @param chart the Chart2D panel
     */
    protected void showChartFrame(Chart2D chart) {
        JFrame frame = new JFrame("2D Chart for " + layerName);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        frame.getContentPane().add(chart, BorderLayout.CENTER);

        frame.setSize(400, 400);

        frame.setLocationRelativeTo(null);

        frame.setVisible(true);
    }

}
